package com.concurrent.sample.business.service.impl;

import com.concurrent.sample.business.dto.CashFlowMessageDTO;
import com.concurrent.sample.business.dto.debug.AppDebugInfoDTO;
import com.concurrent.sample.business.dto.debug.DataMemoryTypeEnum;
import com.concurrent.sample.business.entity.CashFlowEntity;
import com.concurrent.sample.business.repo.CashFlowRepo;
import com.concurrent.sample.business.service.CashFlowService;
import com.concurrent.sample.common.util.BigDecimalUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.PostConstruct;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Slf4j
public class CashFlowServiceImpl implements CashFlowService {

  public static Map<String, CashFlowEntity> CUSTTOMERNO_CONTAINER;

  public static Map<Long, CashFlowEntity> ID_CONTAINER;

  private static AtomicLong idAtomic = new AtomicLong(0);

  private final CashFlowRepo cashFlowRepo;

  @PostConstruct
  public void init(){
    List<CashFlowEntity> list = cashFlowRepo.findAll();
    CUSTTOMERNO_CONTAINER = new ConcurrentHashMap<>();
    ID_CONTAINER = new ConcurrentHashMap<>();
    for(CashFlowEntity cashFlowEntity : list){
      CUSTTOMERNO_CONTAINER.put(cashFlowEntity.getCustomerNo(), cashFlowEntity);
      ID_CONTAINER.put(cashFlowEntity.getId(), cashFlowEntity);
    }
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public CashFlowEntity save(CashFlowEntity param) {
    param.setId(null);

    if(AppDebugInfoDTO.dataMemoryType == DataMemoryTypeEnum.LOCAL){
      param.setId(idAtomic.incrementAndGet());
      param.setVersion(0);
      CUSTTOMERNO_CONTAINER.put(param.getCustomerNo(), param);
      ID_CONTAINER.put(param.getId(), param);
      return param;
    } else {
      return cashFlowRepo.save(param);
    }
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public CashFlowEntity saveWithIncremental(CashFlowMessageDTO param) {
    CashFlowEntity lastCashFlow = findByCustomerNo(param.getCustomerNo());
    CashFlowEntity cashFlowEntity;
    if(Objects.nonNull(lastCashFlow)){
      cashFlowEntity = lastCashFlow;
      log.info("cash flow minus. id = {}, version = {}, src(amount = {}, totalAmount = {}, minus(amount = {}, totalAmount = {})",
          lastCashFlow.getId(), lastCashFlow.getVersion(), lastCashFlow.getAmount(), lastCashFlow.getTotalAmount(),
          param.getAmount(), param.getTotalAmount()
      );
      cashFlowEntity.setSerialNo(lastCashFlow.getSerialNo() + 1);
      cashFlowEntity.setCustomerNo(param.getCustomerNo());
      cashFlowEntity.setAmount(param.getAmount());
      cashFlowEntity.setTotalAmount(BigDecimalUtil.add(lastCashFlow.getTotalAmount(), param.getTotalAmount()));
    } else {
      cashFlowEntity = new CashFlowEntity();
      cashFlowEntity.setSerialNo(1L);
      cashFlowEntity.setCustomerNo(param.getCustomerNo());
      cashFlowEntity.setAmount(param.getAmount());
      cashFlowEntity.setTotalAmount(param.getTotalAmount());
    }
    cashFlowEntity.setMemo(param.getMemo());

    if(AppDebugInfoDTO.dataMemoryType == DataMemoryTypeEnum.LOCAL){
      CUSTTOMERNO_CONTAINER.put(param.getCustomerNo(), cashFlowEntity);
      cashFlowEntity.setVersion(cashFlowEntity.getVersion() + 1);
      ID_CONTAINER.put(cashFlowEntity.getId(), cashFlowEntity);
      return cashFlowEntity;
    } else {
      return cashFlowRepo.save(cashFlowEntity);
    }
  }

  @Override
  public CashFlowEntity getById(Long id) {
    if(AppDebugInfoDTO.dataMemoryType == DataMemoryTypeEnum.LOCAL){
      return ID_CONTAINER.get(id);
    } else {
      return cashFlowRepo.findById(id).orElse(null);
    }
  }

  @Override
  public CashFlowEntity findByCustomerNo(String customerNo) {
    if(AppDebugInfoDTO.dataMemoryType == DataMemoryTypeEnum.LOCAL){
      return CUSTTOMERNO_CONTAINER.get(customerNo);
    } else {
      Specification<CashFlowEntity> specification = new Specification<CashFlowEntity>() {
        @Override
        public Predicate toPredicate(Root<CashFlowEntity> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
          List<Predicate> predicates = new ArrayList<>();
          predicates.add(criteriaBuilder.equal(root.get("customerNo"), customerNo));
          return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
        }
      };
      //表示通过createTime进行 ASC排序
      PageRequest page = PageRequest.of(0, 1, Direction.DESC, "serialNo");
      Page<CashFlowEntity> pageInfo = cashFlowRepo.findAll(specification, page);
      List<CashFlowEntity> cashFlowEntities = pageInfo.getContent();
      return cashFlowEntities.stream().findFirst().orElse(null);
    }
  }

}
